package com.restfinder.common.dao;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.query.AuditQuery;
import org.primefaces.model.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.restfinder.model.IEntity;

@Repository
public abstract class StandardDAO<T extends IEntity> implements Serializable {

	private static final long serialVersionUID = 1L;

	private static final String DISCRIMINATOR_VALUE = "discriminator";
	
	@Autowired
	protected SessionFactory sessionFactory;

	/**
	 * Saves the entity
	 * @param entity must implements {@link IEntity}
	 * @throws Exception
	 */
	public void save(T entity) throws Exception{
		sessionFactory.getCurrentSession().save(entity);
	}

	public void update(T t) throws Exception{
		sessionFactory.getCurrentSession().update(t);
	}

	public void saveOrUpdate(T t) throws Exception{
		sessionFactory.getCurrentSession().saveOrUpdate(t);
	}

	public void delete(T t) throws Exception{
		sessionFactory.getCurrentSession().delete(t);
	}

	public T getById(int id) throws Exception{
		return (T) sessionFactory.getCurrentSession().get(getEntityClass(), id);
	}

	public List<T> list() throws Exception{
		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(getEntityClass());
    	criteria.addOrder(Order.asc("id"));
        List list = criteria.list();
        return list;
	}

	public List<T> list(Class clazz) throws Exception {
		if(getEntitySubclasses()==null || !getEntitySubclasses().contains(clazz)){
			throw new Exception("Class "+clazz.getName()+" is not subclass of "+getEntityClass().getName()+"!");
		}
		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(clazz);
    	criteria.addOrder(Order.asc("id"));
        List list = criteria.list();
        return list;
	}

	public List<T> customList(Class clazz, int first,
			int pageSize, String sortField, SortOrder sortOrder,
			Map<String, String> filters) {
		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(clazz);
		createAliases(criteria, filters, sortField);
	  	addConditionsToCriteria(criteria, filters);
		addOrderToCriteria(criteria, sortField, sortOrder);
		criteria.setMaxResults(pageSize);
		criteria.setFirstResult(first);
		List<T> list = criteria.list();
		return list;
	}

	public String getColumnType(String test){
		return sessionFactory.getClassMetadata(getEntityClass()).getPropertyType(test).getName();
	}

	public List<T> list(int startingAt, int maxPerPage,	String sortField, SortOrder sortOrder, Map<String, String> filters) {
		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(getEntityClass());
		createAliases(criteria, filters, sortField);
	  	addConditionsToCriteria(criteria, filters);
		addOrderToCriteria(criteria, sortField, sortOrder);
		criteria.setMaxResults(maxPerPage);
		criteria.setFirstResult(startingAt);
		List<T> list = criteria.list();
		return list;
	}


	private void createAliases(Criteria criteria, Map<String, String> filters, String sortField) {
		List<String> usedAliases = new ArrayList<String>();
		for(Iterator<String> it = filters.keySet().iterator(); it.hasNext();) {
			String filterProperty = it.next();
            if(filterProperty.contains(".")){
            	String[] strings = filterProperty.split("\\.");
            	if(!usedAliases.contains(strings[0])){
            		createAlias(criteria, usedAliases, strings);
            	}
            }
		}
		if(sortField!=null && !sortField.isEmpty() && sortField.contains(".")){
			String[] strings = sortField.split("\\.");
			if(!usedAliases.contains(strings[0])){
				createAlias(criteria, usedAliases, strings);
			}
		}
	}

	private void createAlias(Criteria criteria, List<String> usedAliases,
			String[] strings) {
		criteria.createAlias(strings[0], strings[0]);
		usedAliases.add(strings[0]);
	}

	public int getRowCount(Map<String, String> filters){
		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(getEntityClass());
		createAliases(criteria, filters, null);
		addConditionsToCriteria(criteria, filters);
		return ((Number) criteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();

	}

	public int getCustomRowCount(Class clazz, Map<String, String> filters){
		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(clazz);
		createAliases(criteria, filters, null);
		addConditionsToCriteria(criteria, filters);
		return ((Number) criteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();

	}

	private void addConditionsToCriteria(Criteria criteria,
			Map<String, String> filters) {

		for(Iterator<String> it = filters.keySet().iterator(); it.hasNext();) {
			String filterProperty = it.next();
            String filterValue = filters.get(filterProperty);

            if(!filterProperty.isEmpty() && !filterProperty.equals(DISCRIMINATOR_VALUE)){
            	if(filterProperty.contains(".")){
            		String[] strings = filterProperty.split("\\.");
            		if(strings[1].equals("id")){
            			criteria.add(Restrictions.eq(filterProperty, Integer.parseInt(filterValue)));
            		}
            		else{
            			criteria.add(Restrictions.like(filterProperty, "%"+filterValue+"%"));
            		}
            	}
            	else{
	               	 if(getColumnType(filterProperty).equalsIgnoreCase("string")){
	                  	criteria.add(Restrictions.like(filterProperty, "%"+filterValue+"%"));
	                  }
	                  else if(getColumnType(filterProperty).equalsIgnoreCase("integer")){
	                  	criteria.add(Restrictions.eq(filterProperty, Integer.parseInt(filterValue)));
	                  }
	                  else if(getColumnType(filterProperty).equalsIgnoreCase("boolean")){
	                  	criteria.add(Restrictions.eq(filterProperty, Boolean.parseBoolean(filterValue)));
	                  }
	                  else if(getColumnType(filterProperty).equalsIgnoreCase("double")){
		                  	criteria.add(Restrictions.eq(filterProperty, Double.parseDouble(filterValue)));
		              }
            	}
          }

		}

	}

	private void addOrderToCriteria(Criteria criteria, String sortField,
			SortOrder sortOrder) {
		if(sortField==null){
			criteria.addOrder(Order.asc("id"));
		}
		else{
			if(sortOrder.name().equalsIgnoreCase("ASCENDING")){
				criteria.addOrder(Order.asc(sortField));
			}
			else{
				criteria.addOrder(Order.desc(sortField));
			}
		}
	}

	public abstract Class getEntityClass();
	public List<Class> getEntitySubclasses(){
		return null;
	}

}